package org.tigr.htc.server.sge;

/*
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.LineNumberReader;
import java.io.StringReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;
import org.tigr.htc.common.HTCConfig;
import org.tigr.antware.shared.util.RunSystemCommand;


/**
 * The <b>SGEUtils</b> class - a grouping of static methods that 
 * interface to Sun Grid Engine
 */
public class SgeUtils {
	static Logger log = Logger.getLogger(SgeUtils.class);

	// location of sge_submit
	final static String htc_submit = HTCConfig.getProperty("sge_submit");

	// location of sge_rm
	final static String htc_rm = HTCConfig.getProperty("sge_rm");

	// location of sge_hold
	final static String htc_hold = HTCConfig.getProperty("sge_hold");

	// location of sge_release
	final static String htc_release = HTCConfig.getProperty("sge_release");

	// location of sudo
	final static String sudo = HTCConfig.getProperty("sudo");

	// password to use for sudo
	final static String passwd = HTCConfig.getProperty("password");

	// should we check if files are on a NFS mount point
	final static String checkNFS = HTCConfig.getProperty("checkNFS");
    
    // Command name to check the completed status 
    final static String htc_complete = HTCConfig.getProperty("sge_complete");

    // Command name to check the running status
    final static String htc_running = HTCConfig.getProperty("sge_running");
    
    final static String jobIDPattern = HTCConfig.getProperty("sge_job_id");
    
    // Command name of the shell wrapper script
    final static String sgeRunScript = HTCConfig.getProperty("sge_run_script");
    
    public final static String htcBase = HTCConfig.getProperty("htc_base");
    
    public final static String htcTestBase = HTCConfig.getProperty("htc_unit_test_base");
    
	/**
	 * The method <code>remove</code> removes the job with the specified ID
     * from the SGE queue
	 * 
	 * @param jobID an <code>int</code> representing the grid Job ID
	 * @exception Exception
	 */
	public static boolean remove(String jobID) throws Exception {
		String sgeCmd = htc_rm + " " + jobID;
        RunSystemCommand run = new RunSystemCommand();
   
        log.info("Removing command from SGE: " + sgeCmd);
        int exitVal = 0;
        try {
            exitVal = run.runCommand(sgeCmd);
        } catch (Throwable e) {
            throw new Exception("Remove failure", e);
        }

        // If submit was not successful throw an exception
        if (exitVal != 0) {
            throw new Exception("Remove Failure. " + run.getErrString());
        }
        
        if (log.isDebugEnabled()) {
            log.debug("Command: " + sgeCmd + " exit value: " + exitVal + " output: "
                    + run.getOutString());
        }

		return true;
	}

	/**
	 * The method <code>hold</code> holds the job with the specified ID from execution
	 * 
	 * @param jobID an <code>int</code> representing the grid Job ID
	 * @exception Exception
	 */
	public static boolean hold(String jobID) throws Exception {
		String sgeCmd = htc_hold + " -h u " + jobID;
		RunSystemCommand run = new RunSystemCommand();
		int exitValue;
		try {
			exitValue = run.runCommand(sgeCmd);
		} catch (Throwable e) {
			throw new Exception(e);
		}

        log.debug("Command: " + sgeCmd + " exit value: " + exitValue);
		return true;
	}

	/**
	 * The method <code>release</code> releases the job with the specified ID
	 * 
	 * @param jobID an <code>int</code> representing the grid Job ID
	 * @exception Exception
	 */
	public static boolean release(String jobID) throws Exception {
		log.debug("In release.");
		String sgeCmd = htc_release + " -h u " + jobID;
		RunSystemCommand run = new RunSystemCommand();
		int exitValue;
		try {
			exitValue = run.runCommand(sgeCmd);
		} catch (Throwable e) {
			throw new Exception(e);
		}

        log.debug("Command: " + sgeCmd +" exit value: " + exitValue);
		return true;
	}

    /**
     * The method <code>submit</code> submits a job to the SGE with the options
     * specified in the submit file as p_user using p_submitFile
     * 
     * @param user a <code>String</code> representing the user name
     * @param workingDir a <code>String</code> representing the working directory
     * @param cwd a <code>File</code> representing the current working directory for the job
     * @param skipNotifyToQsub a <code>boolean</code> representing whether to notify or not
     * 
     * @return a <code>String</code> representing the job ID
     * 
     * @throws Exception
     */
    public static String submit(String user, String workingDir, File cwd, boolean skipNotifyToQsub) throws Exception {
        String jobID = null;

		RunSystemCommand run = new RunSystemCommand();
        
        // As qsub does not handle executable and changing directories very well
        // invoke the wrapper script which will change the directory and the call runSGE
        // to run the actual command so that SGE can transfer runSGE shell script
        String sgeCmd = htc_submit + " " + user + " " + workingDir + " " + cwd.getAbsolutePath() + " " + skipNotifyToQsub;
   
		log.info("Submitting command to SGE: " + sgeCmd);
        int exitVal = 0;
        try {
            exitVal = run.runCommand(sgeCmd);
        } catch (Throwable e) {
            throw new Exception("Submission failure", e);
        }

        log.debug("Command: " + sgeCmd + " exit value: " + exitVal);

        // If submit was not successful throw an exception
        if (exitVal != 0) {
            throw new Exception("Submission Failure. " + run.getErrString());
        }
        
		String line = run.getOutString();
		log.debug("sge_submit result " + line);
		
		LineNumberReader lnr = new LineNumberReader(new FileReader(new File(cwd, "sge_submit.out")));
	    StringBuffer sge_output = new StringBuffer();
	    
	    while ((line = lnr.readLine()) != null) {
	    	sge_output.append(line).append("\n");
	    }
	    lnr.close();
	    
    
    	line = sge_output.toString();
    	log.debug("sge_submit result " + line);

        // Read the job ID from the grabbed output string
        Pattern pattern = Pattern.compile(jobIDPattern);
        Matcher m = pattern.matcher(line);
        if ( m.find()) {
        	jobID = m.group(1);
        } else {
            throw new Exception("Submission Error with submitting job to cluster" 
				+ "Could not parse jobID from submission output: " + line);
        }

        log.debug(" jobID was " + jobID);
		return jobID;
    }
    
    /**
     * The method <code>status</code> returns the status for the job with the specified
     * ID. The method uses <code>qstat</code> to see if the job is idle or running, and
     * the <code>qacct</code> command to get the successful state and return value
     * 
     * @param p_jobID a <code>int</code> representing the job ID
     * 
     * @return a <code>SGEJobStatus</code> that represents the status of the job
     * 
     * @throws Exception
     */
    public static SgeJobStatus status(String p_jobID) throws Exception {
        SgeJobStatus status = new SgeJobStatus();
        
        // First see if the job is either in idle state or running by executing the
        // qstat command. If the job is either in idle state or running then qstat
        // should return with 0 exit status and the status will be printed to STDOUT
        String sgeCmd = htc_running;
        StringReader out = null;
        BufferedReader bufReader = null;
        
        RunSystemCommand run = new RunSystemCommand();
        int exitValue;
		try {
			exitValue = run.runCommand(sgeCmd);	
			out = new StringReader(run.getOutString());
			bufReader = new BufferedReader(out);
		} catch (Throwable e) {
			throw new Exception("Unable to run " + sgeCmd);
		}
        
        log.debug("Command: " + sgeCmd + " exit value: " + exitValue);
        
        // If the return value is zero that means that this job is either idle or
        // running. Determine that based on the output string
        if (exitValue == 0) {
            String jobID = p_jobID;
            String output = null;
            String host = null;
            
            while ((output = bufReader.readLine()) != null) {
            	// If this line begins with jobID then this is the header line so ignore
                if (output.startsWith("job")) continue;
					
				// Else strip the leading white space and see if the line matches
                // the job ID, if not then continue
                output = output.trim();
                if (!output.startsWith(jobID)) continue;
                
                // Else this line corresponds to the specified jobID, so split the 
                // fields
                log.debug("Status line: " + output);
                String tokens [] = output.split("\\s+");
                String state = tokens[4];
                
                if (state.equals("r") || state.equals("t")) {
                	status.setRunning(true);
                } else if (state.startsWith("h")) {
                    status.setHeld(true);
                } else {
                    status.setIdle(true);
                }
                
                // If the number of tokens is greater than 7 then a host name is
                // known so use that
                if (tokens.length > 7) {
                	host = tokens[7];
                    status.setHost(host);
                }
                
                // If an entry was found then return with the status
                log.debug("Status of job with ID: " + p_jobID + " is " + status);
                return status;
            }
		} else {
        	// If there was an error executing the qstat command then throw an exception
            throw new Exception("Could not check the status for job with ID: " + p_jobID);
        }
        
        // If it comes to this point then there were no entries in the qstat output, so
        // check the qacct to retrieve the needed information
        sgeCmd = htc_complete + " -j " + p_jobID;
        
        try {
			exitValue = run.runCommand(sgeCmd);
		} catch (Throwable e) {
			throw new Exception("Unable to run " + sgeCmd);
		}
        out = new StringReader(run.getOutString());
        bufReader = new BufferedReader(out);

        log.debug("Command: " + sgeCmd + " exited with value: " + exitValue);
        
        // If the return value is zero that means that this job is either idle or
        // running. Determine that based on the output string
        if (exitValue == 0) {
            String output = null;
            boolean failedChecked = false;
            boolean exitStatusChecked = false;
            
            while (((output = bufReader.readLine()) != null)
				&& ! failedChecked
				&& ! exitStatusChecked) {
                
                // See if the line starts with 'failed' string to get the failed status
                if (output.startsWith("failed")) {
                    String tokens [] = output.split("\\s");
                    if (tokens[1].equals("1")) {
                    	status.setSuccessful(false);
                    } else {
                    	status.setSuccessful(true);
                    }
                    status.setComplete(true);
                    failedChecked = true;
				}
                
                // See if the line starts with 'exit_status' string to get the exit value
                if (output.startsWith("exit_status")) {
                    String tokens [] = output.split("\\s");
                    String exit = tokens[1];
                    status.setReturnValue(Integer.parseInt(exit));
                    exitStatusChecked = true;
                }
            }
        } else {
            // If there was an error executing the qstat command then throw an exception
            throw new Exception("Could not check the status for job with ID: " + p_jobID);
        }

        log.debug("Status of job with ID: " + p_jobID + " is " + status);
        return status;
    }
    
    public static void main(String[] args) throws Throwable {
		String type = args[0];
        int i = 20;
        String [] jobIDs = new String[i];
        
        if (type.equals("submit")) {
            // Put a hold on two jobs
            for (int j = jobIDs.length - 4; j < jobIDs.length - 2; j++) {
                SgeUtils.hold(jobIDs[j]);           
                SgeUtils.status(jobIDs[j]);
            }
            
            // Remove two jobs
            for (int j = jobIDs.length - 2; j < jobIDs.length; j++) {
                SgeUtils.remove(jobIDs[j]);
            }
            
            boolean done = false;
            SgeJobStatus status = null;
            
            while (! done) {
                done = true;
                for (int j = 0; j < jobIDs.length - 4; j++) {
					status = SgeUtils.status(jobIDs[j]);
                    done = done && status.isComplete();
				}

                Thread.sleep(5000);
			}
            
            // Release hold on two jobs
            for (int j = jobIDs.length - 4; j < jobIDs.length - 2; j++) {
                SgeUtils.release(jobIDs[j]);
            }
            done = false;
            
            while (! done) {
                done = true;
                for (int j = jobIDs.length - 4; j < jobIDs.length - 2; j++) {
                    status = SgeUtils.status(jobIDs[j]);
                    done = done && status.isComplete();
                }

                Thread.sleep(5000);
            }
		}
	}
	
    public static String getTimeinHHmmSS(long minutes){
    	log.debug("Converting " + minutes + " to HH:mm:SS format." );
    	
    	// Convert the minutes to 'HH:mm" format by determining the
    	// number of hours and minutes
    	String timeStr = null;
    	
    	// Get hours
    	long hours = minutes/60;
    	
    	// Get the remaining minutes
    	long min = minutes%60;
    	
    	// Build the time string. Seconds are always set to 00
    	timeStr = "" + hours + ":" + min + ":00";
    	log.debug(minutes + " converted to " + timeStr);
     	return timeStr;
    }
}
